import time
from typing import Dict, Any

import numpy as np
import sympy
from scipy.optimize import root

from app.models.TimeSequenceModel import TimeSequenceModel


class MOMethod:
    def __init__(self, failure_dataframe, precision):
        self.L_b = None
        self.dL_b_db = None
        self.dL_a_db = None
        self.dL_b_da = None
        self.dL_a_da = None
        self.L_a = None
        self.__para = {"a": None, "b": None}
        self.__fail = failure_dataframe["value"]
        self.__diff_fail = TimeSequenceModel \
            .differ_fail(failure_dataframe)["value"].tolist()
        self.__precision = precision

    @staticmethod
    def get_para(a, b) -> Dict[str, Any]:
        return {"a": a, "b": b}

    @staticmethod
    def get_numerical_z(para, t, dt) -> float:
        a = para["a"]
        b = para["b"]
        return a * b / (b * (t + dt) + 1)

    @staticmethod
    def get_numerical_R(para, t, dt) -> float:
        a = para["a"]
        b = para["b"]
        return ((b * t + 1) / (b * (t + dt) + 1)) ** a

    @staticmethod
    def get_numerical_F(para, t, dt) -> float:
        a = para["a"]
        b = para["b"]
        return 1 - MOMethod.get_numerical_R(para, t, dt)

    @staticmethod
    def get_numerical_f(para, t, dt) -> float:
        a = para["a"]
        b = para["b"]
        return a * b * ((b * t + 1) ** a) / (b * (t + dt) + 1) ** (a + 1)

    @staticmethod
    def get_symbol_miu():
        a, b, t = sympy.symbols("a b t", position=True)
        return a * b / (b * t + 1)

    @staticmethod
    def get_symbol_R():
        a, b, t, dt = sympy.symbols("a b t dt", position=True)
        return ((b * t + 1) / (b * (t + dt) + 1)) ** a

    @staticmethod
    def get_symbol_F():
        return 1 - MOMethod.get_symbol_R()

    @staticmethod
    def get_symbol_f():
        dt = sympy.symbols("dt", position=True)
        return sympy.diff(MOMethod.get_symbol_R(), dt)

    @staticmethod
    def loading(drop_frame) -> Any:
        time.sleep(drop_frame)

    def get_MLE(self) -> Any:
        a, b, t = sympy.symbols("a b t", positive=True)
        fail_list = self.__fail[0: 5]
        L = np.prod([self.get_symbol_miu().subs(t, item) for item in fail_list])
        self.L_a = sympy.diff(sympy.expand_log(sympy.log(L)), a)
        self.L_b = sympy.diff(sympy.expand_log(sympy.log(L)), b)
        self.dL_a_da = sympy.diff(self.L_a, a)
        self.dL_a_db = sympy.diff(self.L_a, b)
        self.dL_b_da = sympy.diff(self.L_a, a)
        self.dL_b_db = sympy.diff(self.L_a, b)

    def func_index(self, x):
        a, b = sympy.symbols("a, b", position=True)
        para = {a: x[0], b: x[1]}
        func = [self.L_a.subs(para), self.L_b.subs(para)]
        differ_func = [[self.dL_a_da.subs(para), self.dL_a_db.subs(para)],
                       [self.dL_b_da.subs(para), self.dL_b_db.subs(para)]]
        return func, differ_func

    def get_para_estimate(self):
        sol = root(self.func_index, [1, 1], jac=True, method="lm")
        print(sol.x)
